﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Aga.Controls.Tree;
using System.IO;
using System.Reflection;

namespace UnrealDebugger.Panels
{
    public partial class WatchPanel : Panel
    {
        #region WatchTreeModel

        public class WatchNode : Node
        {
            public WatchNode(string name, string value)
                : base(name)
            {
                this._value = value;
            }

            public int Id { get; set; }
            public int ParentId { get; set; }
            public string Value 
            {
                get
                {
                    if (string.Compare(_value, "Struct", true) == 0)
                    {
                        StringBuilder builder = new StringBuilder(50, 100);
                        builder.Append("{");

                        try
                        {
                            bool firstNode = true;
                            foreach (WatchNode node in this.Nodes)
                            {
                                if (firstNode) { builder.AppendFormat("{0}={1}", node.Variable, node._value); firstNode = false; }
                                else { builder.AppendFormat(", {0}={1}", node.Variable, node._value); }
                            }

                            builder.Append("}");
                        }
                        catch (ArgumentOutOfRangeException /*e*/)
                        {
                            builder.Remove(builder.Length - 4, 4);
                            builder.Append("...}");
                        }

                        return builder.ToString();
                    }
                    else
                    {
                        return _value;
                    }
                }

                set 
                { 
                    if (!UnrealDebuggerIDE.Instance.IsWaitingForUDKResponse())
                    {
                        UnrealDebuggerIDE.Memory.WriteValueToMemory(this.Type, value, this.Address);
                        this._value = value;
                    }
                } 
            }

            public string Variable
            {
                get
                {
                    SplitTokens();
                    return _tokens.Length > 0 ? _tokens[0] : this.Text;
                }
            }

            public string Type 
            {
                get
                {
                    SplitTokens();
                    return _tokens.Length > 1 ? _tokens[1] : "";
                }
            }

            public DataTypes DataType
            {
                get
                {
                    DataTypes convertedType = DataTypes.Bool;
                    Enum.TryParse<DataTypes>(this.Type, true, out convertedType);
                    return convertedType;
                }
            }

            public string Address
            {
                get
                {
                    SplitTokens();

                    if (this.DataType == DataTypes.Bool)
                    {
                        int offset = 0;
                        WatchNode node = this.PreviousNode as WatchNode;
                        while (node != null && node.DataType == DataTypes.Bool)
                        {
                            node = node.PreviousNode as WatchNode;
                            offset = (offset + 1) % 32;
                        }

                        return _tokens.Length > 3 ? string.Format("{0}:{1}", _tokens[3], offset) : "";
                    }
                    
                    return _tokens.Length > 3 ? _tokens[3] : "";
                }
            }

            public bool IsClass
            {
                get
                {
                    return this.Type == ""; 
                }
            }

            public bool IsStruct
            {
                get
                {
                    return this.DataType == DataTypes.Struct;
                }
            }

            public Image Icon
            {
                get
                {
                    DataTypes dataType = DataTypes.Undefined;
                    Enum.TryParse<DataTypes>(Type, true, out dataType);
                    return _dataTypes[(int)dataType];
                }
            }

            private string _value = null;
            private string[] _tokens = null;
            private void SplitTokens()
            {
                if (_tokens == null)
                {
                    _tokens = this.Text.Split(new char[] { '(', ',', ')' }, StringSplitOptions.RemoveEmptyEntries);
                    for (int idx = 0; idx < _tokens.Length; ++idx) { _tokens[idx] = _tokens[idx].Trim(); }
                }
            }

            public enum DataTypes
            {
                Undefined,
                Bool,
                Byte,
                Int,
                Float,
                Enum,
                Struct,
                Object,
                Array,
                String,
                Name,
                Class,
                Delegate,
                Interface,
                DataTypeCount,
            }

            private static Image[] _dataTypes = new Image[(int) DataTypes.DataTypeCount];

            static WatchNode()
            {
                Stream file = null;
                Assembly assembly = typeof(WatchNode).Assembly;

                for (int idx = 0; idx < (int)DataTypes.DataTypeCount; ++idx)
                {
                    file = assembly.GetManifestResourceStream(string.Format("UnrealDebugger.Resources.Type{0}.png", idx));
                    _dataTypes[idx] = Image.FromStream(file);
                }
            }
        }

        public class WatchTreeModel : TreeModelBase
        {
            private UnlockListAction.WatchList _buffer = null;
            private Dictionary<int, WatchNode> _watches = new Dictionary<int, WatchNode>();
            private TreeModel _model = null;

            public WatchTreeModel(UnlockListAction.WatchList buffer)
            {
                this._model = new TreeModel();
                this._buffer = buffer != null ? buffer : new UnlockListAction.WatchList();
            }

            public override System.Collections.IEnumerable GetChildren(TreePath treePath)
            {
                if (this._buffer != null)
                {
                    foreach (UnlockListAction.Watch watch in _buffer)
                    {
                        WatchNode node = new WatchNode(watch.VarName, watch.VarValue);
                        node.Id = watch.Index;
                        node.ParentId = watch.ParentIndex;

                        this._watches[watch.Index] = node;

                        WatchNode parent = null;
                        _watches.TryGetValue(watch.ParentIndex, out parent);

                        if (parent != null)
                        {
                            parent.Nodes.Add(node);
                        }
                        else
                        {
                            this._model.Nodes.Add(node);
                        }
                    }

                    this._buffer = null;

                    this._model.NodesChanged += new EventHandler<TreeModelEventArgs>(TreeNodesChanged);
                    this._model.NodesInserted += new EventHandler<TreeModelEventArgs>(TreeNodesInserted);
                    this._model.NodesRemoved += new EventHandler<TreeModelEventArgs>(TreeNodesRemoved);
                    this._model.StructureChanged += new EventHandler<TreePathEventArgs>(TreeStructureChanged);
                }

                if (treePath.FullPath.Length <= 0)
                {
                    foreach (Node child in _model.Nodes)
                    {
                        yield return child;
                    }
                }
                else
                {
                    Node node = treePath.LastNode as Node;

                    if (node != null)
                    {
                        foreach (Node child in node.Nodes)
                        {
                            yield return child;
                        }
                    }
                }
            }

            public override bool IsLeaf(TreePath treePath)
            {
                Node node = treePath.LastNode as Node;

                return node == null || node.Nodes.Count == 0;
            }

            private void TreeStructureChanged(object sender, TreePathEventArgs e)
            {
                OnStructureChanged(e);
            }

            private void TreeNodesRemoved(object sender, TreeModelEventArgs e)
            {
                OnNodesRemoved(e);
            }

            private void TreeNodesInserted(object sender, TreeModelEventArgs e)
            {
                OnNodesInserted(e);
            }

            private void TreeNodesChanged(object sender, TreeModelEventArgs e)
            {
                OnNodesChanged(e);
            }
        }

        #endregion

        public WatchPanel()
            : base()
        {
            InitializeComponent();
            this.treeViewWatches.Model = null;
            this.nodeTextBoxValue.IsEditEnabledValueNeeded += new EventHandler<Aga.Controls.Tree.NodeControls.NodeControlValueEventArgs>(IsEditEnabledValueNeeded);
            UnrealDebuggerIDE.Instance.OnDebuggerDetached += new UnrealDebuggerIDE.DebuggerDetachedDelegate(OnDebuggerDetached);
        }

        void IsEditEnabledValueNeeded(object sender, Aga.Controls.Tree.NodeControls.NodeControlValueEventArgs e)
        {
            e.Value = false;

            if (!UnrealDebuggerIDE.Instance.IsWaitingForUDKResponse() && UnrealDebuggerIDE.Instance.Project.EditWatchValues)
            {
                WatchNode watchNode = e.Node != null ? e.Node.Tag as WatchNode : null;

                WatchNode.DataTypes type = WatchNode.DataTypes.Bool;
                if (watchNode != null && Enum.TryParse<WatchNode.DataTypes>(watchNode.Type, true, out type))
                {
                    switch (type)
                    {
                        case WatchNode.DataTypes.Bool:
                            {
                                WatchNode parentWatchNode = e.Node.Parent != null ? e.Node.Parent.Tag as WatchNode : null;
                                e.Value = parentWatchNode != null && (parentWatchNode.IsClass || parentWatchNode.IsStruct);
                            } break;

                        case WatchNode.DataTypes.Int:
                        case WatchNode.DataTypes.Float:
                            {
                                e.Value = true;
                            } break;
                    }
                }
            }
        }

        void OnDebuggerDetached()
        {
            this.treeViewWatches.BeginUpdate();
            this.treeViewWatches.Model = null;
            this.treeViewWatches.EndUpdate();
        }

        protected virtual ContextMenuStrip SelectContextMenuForNode(TreeNodeAdv node)
        {
            if (node != null)
            {
                return this.contextMenuStripWatch;
            }
            else
            {
                return null;
            }
        }

        protected virtual void AddUserWatchFromNode(TreeNodeAdv node)
        {
            string varName = "";

            while (node != node.Tree.Root)
            {
                WatchNode watchNode = node != null ? node.Tag as WatchNode : null;
                node = node.Parent;

                if (watchNode == null) continue;
                if (watchNode.Variable.StartsWith("[[")) continue;
                if (varName.Length > 0 && varName[0] != '[') { varName = "." + varName; }

                int position = watchNode.Variable.LastIndexOf('[');

                if (position >= 0 && node != node.Tree.Root)
                {
                    varName = watchNode.Variable.Substring(position, watchNode.Variable.Length - position) + varName;
                }
                else
                {
                    varName = watchNode.Variable + varName;
                }
            }

            if (varName.Length > 0) { UnrealDebuggerIDE.Commands.AddWatch(varName); }
        }

        #region Debugger Interface

        public virtual void ClearAWatch()
        {
            this.treeViewWatches.BeginUpdate();
            this.treeViewWatches.Model = null;
            this.treeViewWatches.EndUpdate();
        }

        public virtual void LockList()
        {
            this.treeViewWatches.BeginUpdate();
            this.treeViewWatches.Model = null;
            this.treeViewWatches.EndUpdate();
        }

        public virtual void UnlockList(UnlockListAction.WatchList buffer)
        {
            this.treeViewWatches.BeginUpdate();
            this.treeViewWatches.Model = new WatchTreeModel(buffer);
            this.treeViewWatches.EndUpdate();
        }

        #endregion

        private void treeViewWatches_MouseDown(object sender, MouseEventArgs e)
        {
            if ((e.Button & MouseButtons.Right) == MouseButtons.Right)
            {
                this.treeViewWatches.ContextMenuStrip = SelectContextMenuForNode(this.treeViewWatches.SelectedNode);
            }
        }

        private void addUserWatchToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AddUserWatchFromNode(this.treeViewWatches.SelectedNode);
        }
    }
}
